﻿#if NETSTANDARD2_0 || NETCOREAPP3_0
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Collections.Concurrent;
#endif
using System;

namespace Longbow.Logging
{
    /// <summary>
    /// 日志文件操作类，内部使用单件模式，所以只能记录在一个文件中
    /// </summary>
#if NETSTANDARD2_0 || NETCOREAPP3_0
    [ProviderAlias("LgbFile")]
#endif
    public sealed class FileLoggerProvider : LoggerProvider
    {
#if NETSTANDARD2_0 || NETCOREAPP3_0
        private readonly ConcurrentDictionary<string, FileLogger> _loggers = new ConcurrentDictionary<string, FileLogger>();
        private IDisposable _optionsReloadToken;
#endif
        private FileLoggerOptions _options;
        private FileLoggerWriter _logWriter;

        /// <summary>
        /// 默认构造函数
        /// </summary>
        /// <param name="options">IFileLoggerOptions 实例</param>
        /// <param name="filter">日志过滤回调函数</param>
        public FileLoggerProvider(FileLoggerOptions options, Func<string, LogLevel, bool> filter = null) : base(null, filter)
        {
            _options = options ?? new FileLoggerOptions();
            _logWriter = new FileLoggerWriter(_options);
            LogCallback = _logWriter.WriteMessage;
        }

#if NETSTANDARD2_0 || NETCOREAPP3_0
        /// <summary>
        /// 通过注入方式监听配置文件初始化 FileProvider，此构造函数被 IoC 调用
        /// </summary>
        /// <param name="options"></param>
        public FileLoggerProvider(IOptionsMonitor<FileLoggerOptions> options) : this(options.CurrentValue)
        {
            _optionsReloadToken = options.OnChange(ReloadLoggerOptions);
        }

        private void ReloadLoggerOptions(FileLoggerOptions op)
        {
            if (Equals(op)) return;
            _options = op;
            var scoper = GetScopeProvider();
            foreach (var logger in _loggers.Values)
            {
                logger.ScopeProvider = scoper;
                _logWriter.Option = op;
            }
        }

        private IExternalScopeProvider scopeProvider;

        private IExternalScopeProvider GetScopeProvider()
        {
            if (_options.IncludeScopes && scopeProvider == null)
            {
                scopeProvider = new LoggerExternalScopeProvider();
            }
            return _options.IncludeScopes ? scopeProvider : null;
        }

        /// <summary>
        ///创建 ILogger 实例方法
        /// </summary>
        /// <param name="categoryName">分类名称</param>
        /// <returns>ILogger 实例</returns>
        public override ILogger CreateLogger(string categoryName) => _loggers.GetOrAdd(categoryName, name => new FileLogger(name, LogCallback, Filter, GetScopeProvider()));
#else
        /// <summary>
        /// 创建 ILogger 实例方法
        /// </summary>
        /// <param name="categoryName">分类名称</param>
        /// <returns>ILogger 实例</returns>
        public override ILogger CreateLogger(string categoryName) => new FileLogger(categoryName, LogCallback, Filter, _options.IncludeScopes);
#endif

        /// <summary>
        /// 强制输出日志到文件中
        /// </summary>
        public void Flush() => _logWriter.Flush();

        private bool disposedValue; // To detect redundant calls

        /// <summary>
        /// Dispose 方法
        /// </summary>
        /// <param name="disposing"></param>
        protected override void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
#if NETSTANDARD2_0 || NETCOREAPP3_0
                    if (_optionsReloadToken != null) _optionsReloadToken.Dispose();
#endif
                    _logWriter.Dispose();
                }
                disposedValue = true;
            }
        }

#if NETSTANDARD2_0 || NETCOREAPP3_0
        private bool Equals(FileLoggerOptions op) => op != null
            && _options != null
            && _options.IncludeScopes == op.IncludeScopes
            && _options.FileName == op.FileName
            && _options.MaxFileCount == op.MaxFileCount
            && _options.Batch == op.Batch
            && _options.BatchCount == op.BatchCount
            && _options.TaskWaitTime.Ticks == op.TaskWaitTime.Ticks
            && _options.Interval == op.Interval;
#endif
    }
}
